﻿/* 
   Copyright 2011 LUCA Studios LLC & John Evans

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

namespace LUCAStudios.Silverlight.Controls
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Markup;
    using Microsoft.Expression.Interactivity.Core;
    using System.Windows.Media;

    /// <summary>
    /// Provides a standard menu strip control for use within the LUCA UI framework.
    /// This control is unique, in that it can be used outside of a LUCALayoutPanel context
    /// </summary>
    [ContentProperty("Children")]
    [TemplatePart(Name = LUCAMenuStripRoot, Type = typeof(Grid))]
    [TemplatePart(Name = LUCAMenuStripTitleWrapper, Type = typeof(Grid))]
    [TemplatePart(Name = LUCAMenuStripContentPresenter, Type = typeof(ContentPresenter))]
    public class LUCAMenuStrip : MenuBase
    {
        private ItemsControl _ic;
        private bool _icEventSet;
        private bool _hoverShow;
        private bool _isWindowBaseMenu;
        private Grid _LUCAMenuStripTitleWrapper;
        //using button in order to more easily eat the mousedown/up events when user clicks off the menu
        private readonly Button _cover = new Button();       
       
        //template part constants
        private const string LUCAMenuStripRoot = "LUCALUCAMenuStripRoot";
        private const string LUCAMenuStripContentPresenter = "LUCALUCAMenuStripContentPresenter";
        private const string LUCAMenuStripTitleWrapper = "LUCALUCAMenuStripTitleWrapper";

        internal Panel MenuLayer { get; private set; }

        /// <summary>
        /// Constructor
        /// </summary>
        public LUCAMenuStrip()
        {
            DefaultStyleKey = typeof(LUCAMenuStrip);
            Titles = new ObservableCollection<string>();
            Grid.SetRow(_cover, 1);
        }

        /// <summary>
        /// Called when the LUCA control is loaded
        /// </summary>
        protected override void OnLoaded()
        {
            base.OnLoaded();

            UpdateStripLayout();
            
            if (LayoutContext != null)
            {
                MenuLayer = LayoutContext.RootVisual;
                Grid.SetRow(_cover, 1);
            }

            var newStyle = (Style)XamlReader.Load("<Style xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" x:Key=\"invisiblePlainButton\" TargetType=\"Button\"><Setter Property=\"Template\"><Setter.Value><ControlTemplate TargetType=\"Button\"><Grid Background=\"Transparent\"></Grid></ControlTemplate></Setter.Value></Setter></Style>");
            //var newStyle = (Style)XamlReader.Load("<Style xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" x:Key=\"invisiblePlainButton\" TargetType=\"Button\"><Setter Property=\"Template\"><Setter.Value><ControlTemplate TargetType=\"Button\"><Grid Opacity=\".5\" Background=\"Blue\"></Grid></ControlTemplate></Setter.Value></Setter></Style>");

            _cover.Style = newStyle;
            Grid.SetColumn(_cover, 99);
            Grid.SetRowSpan(_cover, 99);

            if (_icEventSet) return;
            _ic = this.FindElement<ItemsControl>();
            if (_ic == null) return;
            _ic.LayoutUpdated += IcLayoutUpdated;
            _icEventSet = true;
        }

        void IcLayoutUpdated(object sender, EventArgs e)
        {
            _ic.LayoutUpdated -= IcLayoutUpdated;
            IcLoaded();
        }

        private void UpdateStripLayout()
        {
            Titles.Clear();

            CorrectChildren
                .ToObservable()
                .Subscribe(c =>
                               {
                                   var menu = c as Menu;
                                   if (menu != null) Titles.Add(menu.Title);
                                   c.ParentMenuElement = this;
                               });
        }


        //ignores children that are not of the correct type
        private IEnumerable<MenuBase> CorrectChildren
        {
            get
            {
                return Children
                          .Where(o => o is Menu)
                          .ToList();
            }
        }

        void IcLoaded()
        {
            if (this.FindParentByType<WindowBase>() != null)
            {
                _isWindowBaseMenu = true;

                //we know we're in a LiquidLayout situation so move the cover up to cover any LUCALayoutPanel menu, if there is one
                Grid.SetRow(_cover, 0);
            }

            _LUCAMenuStripTitleWrapper.MouseLeftButtonDown += (_, __) =>
                                                     {
                                                         _hoverShow = false;
                                                         HideAllMenus();
                                                     };

            _ic
                .FindAllElementsByType<Grid>()
                .Subscribe(c =>
                               {
                                   var text = c.FindElement<TextBlock>().Text;
                                   var i = _ic.Items.IndexOf(text);

                                   c.MouseEnter += (_, __) =>
                                       {
                                           if (_hoverShow)
                                           {
                                               ShowMenu(c, CorrectChildren.ElementAt(i) as Menu, true);
                                           }
                                           else
                                           {
                                               ExtendedVisualStateManager.GoToElementState(c, "Hover", true);
                                           }
                                       };

                                   c.MouseLeave += (_, __) => ExtendedVisualStateManager.GoToElementState(c, "Normal", true);

                                   c.MouseLeftButtonDown += (_, args) =>
                                        {
                                            ShowMenu(c, CorrectChildren.ElementAt(i) as Menu, true);
                                            args.Handled = true;
                                        };

                               });

        }

        private void ShowMenu(FrameworkElement menuParent, Menu menuToShow, bool hideOtherMenus)
        {

            if (hideOtherMenus)
                HideAllMenus();

            var menuParentPosition = menuParent.GetPosition(Application.Current.RootVisual);

            var adjustedPosition = new Point(menuParentPosition.X - 1, menuParentPosition.Y - 2);

            var pGrid = LayoutContext != null ? LayoutContext.FindElement<Grid>() : MenuLayer;

            ChangeLayoutHitTest(false);

            pGrid.Children.Add(_cover);

            _cover.Click += CoverClick;

            MenuLayer.Children.Add(menuToShow);

            menuToShow.AbsoluteTransformPositionTo(adjustedPosition, Application.Current.RootVisual);

            _hoverShow = true;

            if (_isWindowBaseMenu)
                AddProxyMenuUI(menuToShow);
        }

        private void AddProxyMenuUI(Menu currentMenu)
        {

            _ic
                .FindAllElementsByType<Grid>()
                .ObserveOnDispatcher()
                .Subscribe(c =>
                {
                    var text = c.FindElement<TextBlock>();
                    var i = _ic.Items.IndexOf(text.Text);

                    //skip the current menu
                    var m = CorrectChildren.ElementAt(i) as Menu;
                    if (m != null && m.Equals(currentMenu)) return;

                    var menuParentPosition = c.GetPosition(Application.Current.RootVisual);

                    var adjustedPosition = new Point(menuParentPosition.X + 7, menuParentPosition.Y);

                    var ng = new Grid
                    {
                        Background = new SolidColorBrush(Colors.Transparent),
                        Width = text.ActualWidth,
                        Height = 22,
                        HorizontalAlignment = HorizontalAlignment.Left,
                        VerticalAlignment = VerticalAlignment.Top,
                        Tag = "proxy"
                    };

                    ng.MouseEnter += (_, __) =>
                    {
                        if (_hoverShow)
                        {
                            ShowMenu(c, CorrectChildren.ElementAt(i) as Menu, true);
                        }
                        else
                        {
                            ExtendedVisualStateManager.GoToElementState(c, "Hover", true);
                        }
                    };

                    ng.MouseLeave += (_, __) =>
                        ExtendedVisualStateManager.GoToElementState(c, "Normal", true);

                    ng.MouseLeftButtonDown += (_, args) =>
                    {
                        ShowMenu(c, CorrectChildren.ElementAt(i) as Menu, true);
                        args.Handled = true;
                    };

                    MenuLayer.Children.Add(ng);
                    ng.AbsoluteTransformPositionTo(adjustedPosition, Application.Current.RootVisual);

                });
        }

        //adjust hit test visibility on layout elements (except the LUCAMenuStrip)
        //this prevents cases where user may have elements overlapping the LUCAMenuStrip itself
        private void ChangeLayoutHitTest(bool setting)
        {
            if (LayoutContext != null)
                LayoutContext
                    .Children
                    .ToObservable()
                    .Where(o => !(o is LUCAMenuStrip))
                    .Subscribe(o => o.IsHitTestVisible = setting);
        }

        void CoverClick(object sender, RoutedEventArgs e)
        {
            _cover.Click -= CoverClick;
            ResetLUCAMenuStrip(this);
        }

        private void HideAllMenus()
        {
            //remove any menu's associated with this menu strip and also any proxy ui, if exists
            MenuLayer
                .Children
                .ToObservable()
                .Where(
                    o => 
                        (o is MenuBase && (o as MenuBase).LUCAMenuStripContext.Equals(this))
                        || (o is Grid && (o as Grid).Tag is String && (o as Grid).Tag.ToString() == "proxy")
                )
                .ObserveOnDispatcher()
                .Subscribe(o => MenuLayer.Children.Remove(o));


            var pGrid = LayoutContext != null ? LayoutContext.FindElement<Grid>() : MenuLayer;
            if (pGrid.Children.Contains(_cover))
                pGrid.Children.Remove(_cover);

            ChangeLayoutHitTest(true);
        }

        /// <summary>
        /// Closes all menus currently open from the LUCAMenuStrip that the given source MenuBase is associated with.
        /// </summary>
        /// <param name="source"></param>
        internal static void ResetLUCAMenuStrip(MenuBase source)
        {
            var LUCAMenuStrip = FindLUCAMenuStrip(source);
            LUCAMenuStrip._hoverShow = false;
            LUCAMenuStrip.HideAllMenus();
        }

        internal static LUCAMenuStrip FindLUCAMenuStrip(MenuBase source)
        {
            if (source is LUCAMenuStrip)
            {
                return source as LUCAMenuStrip;
            }

            if (source.ParentMenuElement != null)
            {
                return FindLUCAMenuStrip(source.ParentMenuElement);
            }

            return null;
        }

        #region overrides

        /// <summary>
        /// When overridden in a derived class, is invoked whenever application code or internal processes (such as a rebuilding layout pass) call <see cref="M:System.Windows.Controls.Control.ApplyTemplate"/>. In simplest terms, this means the method is called just before a UI element displays in an application. For more information, see Remarks.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            MenuLayer = GetTemplateChild(LUCAMenuStripRoot) as Grid;
           
            
            _LUCAMenuStripTitleWrapper = GetTemplateChild(LUCAMenuStripTitleWrapper) as Grid;
        }

        /// <summary>
        /// Called when the collection changes
        /// </summary>
        /// <param name="e"></param>
        protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            UpdateStripLayout();
            base.OnCollectionChanged(e);
        }

        #endregion

        #region Dependency Properties

        internal static readonly DependencyProperty TitlesProperty =
            DependencyProperty.Register("Titles", typeof(ObservableCollection<string>), typeof(LUCAMenuStrip), new PropertyMetadata(null));

        //holds the collection of titles that are bound to the control UI
        //this collection is built automatically as LUCAMenu controls are added to the Children collection
        internal ObservableCollection<string> Titles
        {
            get { return (ObservableCollection<string>)GetValue(TitlesProperty); }
            set { SetValue(TitlesProperty, value); }
        }

        /// <summary>
        /// A backing property for the LUCAMenuStripContent dependency property
        /// </summary>
        public static readonly DependencyProperty LUCAMenuStripContentProperty =
            DependencyProperty.Register("LUCAMenuStripContent", typeof(FrameworkElement), typeof(LUCAMenuStrip), new PropertyMetadata(default(FrameworkElement)));

        /// <summary>
        /// When the LUCAMenuStrip is used as a standalone control (outside of a LUCALayoutPanel Context),
        /// This Dependency Property holds the content to be displayed below the menu strip
        /// This is necessary so that the control is able to properly setup it's layering system
        /// for menu overlays, etc
        /// </summary>
        public FrameworkElement LUCAMenuStripContent
        {
            get { return (FrameworkElement)GetValue(LUCAMenuStripContentProperty); }
            set { SetValue(LUCAMenuStripContentProperty, value); }
        }

        #endregion

    }
}
